#pragma once

#include "vec3.h"

OPEN_JLIB_NS


struct Vec4 {

  union {
    struct { f32 x, y, z, w; };
    struct { f32 r, g, b, a; };
    f32v4 p;
  };

  enum class BlendFunc {
    Add, Sub, SubR, Mul, Min, Max,
  };

  enum class BlendParam {
    Zero, One,
    SrcAlpha, DstAlpha,
    OneMinusSrcAlpha, OneMinusDstAlpha,
    SrcColor, DstColor,
    OneMinusSrcColor, OneMinusDstColor,
  };


  inline Vec4 () noexcept = default;
  inline Vec4 (f32v4 p) noexcept : p(p) {
  }
  inline Vec4 (f32 x, f32 y, f32 z, f32 w) noexcept {
    asm volatile (R"(
    zip1 %[dst].2s, %[op1].2s, %[op2].2s
    ins %[dst].s[2], %[op3].s[0]
    ins %[dst].s[3], %[op4].s[0])"
    : [dst]"=&x"(p): [op1]"x"(x), [op2]"x"(y), [op3]"x"(z), [op4]"x"(w));
  }
  inline Vec4 (Vec3 v, f32 w = 0.0F) noexcept {
    asm volatile (R"(
    ins %[dst].s[3], %[op2].s[0])"
    : [dst]"=x"(p): "[dst]"(v.p), [op2]"x"(w));
  }
  inline Vec4 (f32 s) noexcept {
    asm volatile (R"(
    dup %[dst].4s, %[src].s[0])"
    : [dst]"=x"(p): [src]"x"(s));
  }
  inline Vec4 (const f32* m) noexcept {
    asm volatile (R"(
    ld1 { %[dst].4s }, [%[mem]])"
    : [dst]"=x"(p): [mem]"r"(m));
  }


  inline operator Vec3 () const noexcept {
    return p;
  }


  inline f32 operator (uwl i) const noexcept {
    return p[i];
  }


  inline f32 x () const noexcept {
    return p[0];
  }
  inline f32 y () const noexcept {
    return p[1];
  }
  inline f32 z () const noexcept {
    return p[2];
  }
  inline f32 w () const noexcept {
    return p[3];
  }


  inline Vec4& x (f32 s) noexcept {
    p[0] = s;
    return *this;
  }
  inline Vec4& y (f32 s) noexcept {
    p[1] = s;
    return *this;
  }
  inline Vec4& z (f32 s) noexcept {
    p[2] = s;
    return *this;
  }
  inline Vec4& w (f32 s) noexcept {
    p[3] = s;
    return *this;
  }


  inline Vec4& operator+= (Vec4 v) noexcept {
    p += v.p;
    return *this;
  }
  inline Vec4& operator-= (Vec4 v) noexcept {
    p -= v.p;
    return *this;
  }
  inline Vec4& operator*= (Vec4 v) noexcept {
    p *= v.p;
    return *this;
  }
  inline Vec4& operator/= (Vec4 v) noexcept {
    p /= v.p;
    return *this;
  }


  inline Vec4& operator*= (f32 s) noexcept {
    p *= s;
    return *this;
  }
  inline Vec4& operator/= (f32 s) noexcept {
    p /= s;
    return *this;
  }


  inline Vec4& clamp (Vec4 min, Vec4 max) noexcept {
    asm volatile (R"(
    fmin %[dst].4s, %[dst].4s, %[max].4s
    fmax %[dst].4s, %[dst].4s, %[min].4s)"
    : [dst]"+x"(p): [min]"x"(min.p), [max]"x"(max.p));
    return *this;
  }


  inline Vec4 operator+ (Vec4 v) const noexcept {
    return p + v.p;
  }
  inline Vec4 operator- (Vec4 v) const noexcept {
    return p - v.p;
  }
  inline Vec4 operator* (Vec4 v) const noexcept {
    return p * v.p;
  }
  inline Vec4 operator/ (Vec4 v) const noexcept {
    return p / v.p;
  }


  inline Vec4 operator* (f32 s) const noexcept {
    return p * s;
  }
  inline Vec4 operator/ (f32 s) const noexcept {
    return p / s;
  }


  inline Vec4 operator+ () const noexcept {
    f32v4 r;
    asm volatile (R"(
    fabs %[dst].4s, %[src].4s)"
    : [dst]"=x"(r): [src]"x"(p));
    return r;
  }
  inline Vec4 operator- () const noexcept {
    f32v4 r;
    asm volatile (R"(
    fneg %[dst].4s, %[src].4s)"
    : [dst]"=x"(r): [src]"x"(p));
    return r;
  }


  inline Vec4 alpha (f32 alpha) const noexcept {
    f32v4 r;
    asm volatile (R"(
    ins %[dst].s[3], %[com].s[0])"
    : [dst]"=x"(r): "[dst]"(p), [com]"x"(alpha));
    return r;
  }
  inline Vec4 luminance () const noexcept {
    static const f32v4 f = {0.299F, 0.587F, 0.114F, 0.0F};
    f32v4 r;
    asm volatile (R"(
    fmul %[dst].4s, %[op1].4s, %[op2].4s
    faddp %[dst].4s, %[dst].4s, %[dst].4s
    faddp %[dst].4s, %[dst].4s, %[dst].4s
    ins %[dst].s[3], %[op1].s[3])"
    : [dst]"=&x"(r): [op1]"x"(p), [op2]"x"(f));
    return r;
  }


  inline static Vec4 zero () noexcept {
    f32v4 r;
    asm volatile (R"(
    eor %[dst].16b, %[dst].16b, %[dst].16b)"
    : [dst]"=r"(r));
    return r;
  }
  inline static Vec4 one () noexcept {
    f32v4 r;
    asm volatile (R"(
    dup %[dst].4s, %[imm].s[0])"
    : [dst]"=x"(r): [imm]"x"(1.0F));
    return r;
  }
  template<u8 Com>
  inline static Vec4 unit () noexcept;


  inline static Vec4 min (Vec4 a, Vec4 b) noexcept {
    f32v4 r;
    asm volatile (R"(
    fmin %[dst].4s, %[op1].4s, %[op2].4s)"
    : [dst]"=x"(r): [op1]"x"(a.p), [op2]"x"(b.p));
    return r;
  }
  inline static Vec4 max (Vec4 a, Vec4 b) noexcept {
    f32v4 r;
    asm volatile (R"(
    fmax %[dst].4s, %[op1].4s, %[op2].4s)"
    : [dst]"=x"(r): [op1]"x"(a.p), [op2]"x"(b.p));
    return r;
  }
  inline static Vec4 clamp (Vec4 v, Vec4 min, Vec4 max) noexcept {
    return Vec4::min(Vec4::max(v, min), max);
  }


  template<BlendFunc Func>
  inline static Vec4 blend_func (Vec4 src, Vec4 dst) noexcept;

  template<BlendParam Param>
  inline static Vec4 blend_part (Vec4 src, Vec4 dst, Vec4 val) noexcept;

  template<BlendParam Src = BlendParam::SrcAlpha, BlendParam Dst = BlendParam::OneMinusSrcAlpha, BlendFunc Func = BlendFunc::Add>
  inline static Vec4 blend (Vec4 src, Vec4 dst) noexcept;
};


template<>
inline Vec4 Vec4::unit<0> () noexcept {
  f32v4 r;
  asm volatile (R"(
  eor %[dst].16b, %[dst].16b, %[dst].16b
  ins %[dst].s[0], %[imm].s[0])"
  : [dst]"=x"(r): [imm]"x"(1.0F));
  return r;
}
template<>
inline Vec4 Vec4::unit<1> () noexcept {
  f32v4 r;
  asm volatile (R"(
  eor %[dst].16b, %[dst].16b, %[dst].16b
  ins %[dst].s[1], %[imm].s[0])"
  : [dst]"=x"(r): [imm]"x"(1.0F));
  return r;
}
template<>
inline Vec4 Vec4::unit<2> () noexcept {
  f32v4 r;
  asm volatile (R"(
  eor %[dst].16b, %[dst].16b, %[dst].16b
  ins %[dst].s[2], %[imm].s[0])"
  : [dst]"=x"(r): [imm]"x"(1.0F));
  return r;
}
template<>
inline Vec4 Vec4::unit<3> () noexcept {
  f32v4 r;
  asm volatile (R"(
  eor %[dst].16b, %[dst].16b, %[dst].16b
  ins %[dst].s[3], %[imm].s[0])"
  : [dst]"=x"(r): [imm]"x"(1.0F));
  return r;
}


template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Add> (Vec4 src, Vec4 dst) noexcept {
  return src + dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Sub> (Vec4 src, Vec4 dst) noexcept {
  return src - dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::SubR> (Vec4 src, Vec4 dst) noexcept {
  return dst - src;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Mul> (Vec4 src, Vec4 dst) noexcept {
  return src * dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Min> (Vec4 src, Vec4 dst) noexcept {
  return min(src, dst);
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Max> (Vec4 src, Vec4 dst) noexcept {
  return max(src, dst);
}


template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::Zero> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return zero();
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::One> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::SrcAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * src.p[3];
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::DstAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * dst.p[3];
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusSrcAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (1.0F - src.p[3]);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusDstAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (1.0F - dst.p[3]);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::SrcColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * src;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::DstColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * dst;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusSrcColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (one() - src);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusDstColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (one() - dst);
}


template<Vec4::BlendParam Src, Vec4::BlendParam Dst, Vec4::BlendFunc Func>
inline Vec4 Vec4::blend (Vec4 src, Vec4 dst) noexcept {
  auto s = blend_part<Src>(src, dst, src);
  auto d = blend_part<Src>(src, dst, dst);
  return blend_func<Func>(s, d);
}


inline Vec4 operator* (f32 a, Vec4 b) noexcept {
  return b * a;
}


inline Vec4 operator"" _RGBA (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto r = ((i >> 24) & 0xFF) * s;
  auto g = ((i >> 16) & 0xFF) * s;
  auto b = ((i >> 8) & 0xFF) * s;
  auto a = (i & 0xFF) * s;
  return {r, g, b, a};
}

inline Vec4 operator"" _ARGB (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto a = ((i >> 24) & 0xFF) * s;
  auto g = ((i >> 16) & 0xFF) * s;
  auto b = ((i >> 8) & 0xFF) * s;
  auto r = (i & 0xFF) * s;
  return {r, g, b, a};
}


CLOSE_JLIB_NS
